Completed
Pull Request — master (#99)
by
unknown
01:04
created

APIClient.addressUnconfirmedTransactions   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 3
rs 10
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
/**
22
 *
23
 * @param opt
24
 * @returns {*}
25
 */
26
function networkFromOptions(opt) {
27
    if (opt.bitcoinCash) {
28
        if (opt.regtest) {
29
            return bitcoin.networks.bitcoincashregtest;
30
        } else if (opt.testnet) {
31
            return bitcoin.networks.bitcoincashtestnet;
32
        } else {
33
            return bitcoin.networks.bitcoincash;
34
        }
35
    } else {
36
        if (opt.regtest) {
37
            return bitcoin.networks.regtest;
38
        } else if (opt.testnet) {
39
            return bitcoin.networks.testnet;
40
        } else {
41
            return bitcoin.networks.bitcoin;
42
        }
43
    }
44
}
45
46
var useWebWorker = require('./use-webworker')();
47
48
49
/**
50
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
51
 *
52
 * @param promise   {q.Promise}
53
 * @param cb        function
54
 * @return q.Promise
55
 */
56
function callbackify(promise, cb) {
57
    // add a .then to trigger the cb for people using callbacks
58
    if (cb) {
59
        promise
60
            .then(function(res) {
61
                // use q.nextTick for asyncness
62
                q.nextTick(function() {
63
                    cb(null, res);
64
                });
65
            }, function(err) {
66
                // use q.nextTick for asyncness
67
                q.nextTick(function() {
68
                    cb(err, null);
69
                });
70
            });
71
    }
72
73
    // return the promise for people using promises
74
    return promise;
75
}
76
77
/**
78
 * Bindings to consume the BlockTrail API
79
 *
80
 * @param options       object{
81
 *                          apiKey: 'API_KEY',
82
 *                          apiSecret: 'API_SECRET',
83
 *                          host: 'defaults to api.blocktrail.com',
84
 *                          network: 'BTC|LTC',
85
 *                          testnet: true|false
86
 *                      }
87
 * @constructor
88
 */
89
var APIClient = function(options) {
90
    var self = this;
91
92
    // handle constructor call without 'new'
93
    if (!(this instanceof APIClient)) {
94
        return new APIClient(options);
95
    }
96
97
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
98
    options.network = normalizedNetwork[0];
99
    options.testnet = normalizedNetwork[1];
100
    options.regtest = normalizedNetwork[2];
101
    // apiNetwork we allow to be customized for debugging purposes
102
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
103
104
    self.bitcoinCash = options.network === "BCC";
105
    self.regtest = options.regtest;
106
    self.testnet = options.testnet;
107
    self.network = networkFromOptions(self);
108
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
109
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
110
111
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
112
113
    if (typeof options.btccom === "undefined") {
114
        options.btccom = true;
115
    }
116
117
    /**
118
     * @type RestClient
119
     */
120
    self.dataClient = APIClient.initRestClient(_.merge({}, options));
121
122
    /**
123
     * @type RestClient
124
     */
125
    self.blocktrailClient = APIClient.initRestClient(_.merge({}, options, {btccom: false}));
126
127
    if (options.btccom) {
128
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
129
    } else {
130
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
131
    }
132
133
};
134
135
APIClient.normalizeNetworkFromOptions = function(options) {
136
    /* jshint -W071, -W074 */
137
    var network = 'BTC';
138
    var testnet = false;
139
    var regtest = false;
140
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
141
142
    var prefix;
143
    var done = false;
144
145
    if (options.network) {
146
        var lower = options.network.toLowerCase();
147
148
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
149
        if (!m) {
150
            throw new Error("Invalid network [" + options.network + "]");
151
        }
152
153
        if (m[2] === 'btc') {
154
            network = "BTC";
155
        } else {
156
            network = "BCC";
157
        }
158
159
        prefix = m[1];
160
        if (prefix) {
161
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
162
            done = true;
163
            if (prefix === 'r') {
164
                testnet = true;
165
                regtest = true;
166
            } else if (prefix === 't') {
167
                testnet = true;
168
            }
169
        }
170
    }
171
172
    // if we're not already done then apply options.regtest and options.testnet
173
    if (!done) {
174
        if (options.regtest) {
175
            testnet = true;
176
            regtest = true;
177
            prefix = "r";
178
        } else if (options.testnet) {
179
            testnet = true;
180
            prefix = "t";
181
        }
182
    }
183
184
    apiNetwork = (prefix || "") + network;
185
186
    return [network, testnet, regtest, apiNetwork];
187
};
188
189
APIClient.initRestClient = function(options) {
190
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
191
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
192
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
193
    }
194
195
    // trim off leading https?://
196
    if (options.host && options.host.indexOf("https://") === 0) {
197
        options.https = true;
198
        options.host = options.host.substr(8);
199
    } else if (options.host && options.host.indexOf("http://") === 0) {
200
        options.https = false;
201
        options.host = options.host.substr(7);
202
    }
203
204
    if (typeof options.https === "undefined") {
205
        options.https = true;
206
    }
207
208
    if (!options.port) {
209
        options.port = options.https ? 443 : 80;
210
    }
211
212
    if (options.btccom) {
213
        if (!options.host) {
214
            options.host = 'chain.api.btc.com';
215
        }
216
217
        if (!options.endpoint) {
218
            options.endpoint = "/" + (options.apiVersion || "v3");
219
        }
220
221
    } else {
222
        if (!options.host) {
223
            options.host = 'api.blocktrail.com';
224
        }
225
226
        if (!options.endpoint) {
227
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
228
        }
229
    }
230
231
    return new RestClient(options);
232
};
233
234
var determineDataStorageV2_3 = function(options) {
235
    return q.when(options)
236
        .then(function(options) {
237
            // legacy
238
            if (options.storePrimaryMnemonic) {
239
                options.storeDataOnServer = options.storePrimaryMnemonic;
240
            }
241
242
            // storeDataOnServer=false when primarySeed is provided
243
            if (typeof options.storeDataOnServer === "undefined") {
244
                options.storeDataOnServer = !options.primarySeed;
245
            }
246
247
            return options;
248
        });
249
};
250
251
var produceEncryptedDataV2 = function(options, notify) {
252
    return q.when(options)
253
        .then(function(options) {
254
            if (options.storeDataOnServer) {
255
                if (!options.secret) {
256
                    if (!options.passphrase) {
257
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
258
                    }
259
260
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
261
262
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
263
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
264
                }
265
266
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
267
268
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
269
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
270
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
271
272
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
273
274
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
275
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
276
            }
277
278
            return options;
279
        });
280
};
281
282
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
283
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
284
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
285
        var saltBuf = Encryption.generateSalt();
286
        var iv = Encryption.generateIV();
287
288
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
289
            return require('./webworker');
290
        }, onLoadWorkerLoadAsmCrypto, {
291
            method: 'Encryption.encryptWithSaltAndIV',
292
            pt: pt,
293
            pw: pw,
294
            saltBuf: saltBuf,
295
            iv: iv,
296
            iterations: iter
297
        })
298
            .then(function(data) {
299
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
300
            });
301
    } else {
302
        try {
303
            return q.when(Encryption.encrypt(pt, pw, iter));
304
        } catch (e) {
305
            return q.reject(e);
306
        }
307
    }
308
};
309
310
APIClient.prototype.promisedDecrypt = function(ct, pw) {
311
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
312
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
313
            return require('./webworker');
314
        }, onLoadWorkerLoadAsmCrypto, {
315
            method: 'Encryption.decrypt',
316
            ct: ct,
317
            pw: pw
318
        })
319
            .then(function(data) {
320
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
321
            });
322
    } else {
323
        try {
324
            return q.when(Encryption.decrypt(ct, pw));
325
        } catch (e) {
326
            return q.reject(e);
327
        }
328
    }
329
};
330
331
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
332
    var self = this;
333
334
    return q.when(options)
335
        .then(function(options) {
336
            if (options.storeDataOnServer) {
337
                return q.when()
338
                    .then(function() {
339
                        if (!options.secret) {
340
                            if (!options.passphrase) {
341
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
342
                            }
343
344
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
345
346
                            // -> now a buffer
347
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
348
349
                            // -> now a buffer
350
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
351
                                .then(function(encryptedSecret) {
352
                                    options.encryptedSecret = encryptedSecret;
353
                                });
354
                        } else {
355
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
356
                                throw new Error('Secret must be a buffer');
357
                            }
358
                        }
359
                    })
360
                    .then(function() {
361
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
362
363
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
364
                            .then(function(encryptedPrimarySeed) {
365
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
366
                            });
367
                    })
368
                    .then(function() {
369
                        // skip generating recovery secret when explicitly set to false
370
                        if (options.recoverySecret === false) {
371
                            return;
372
                        }
373
374
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
375
                        if (!options.recoverySecret) {
376
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
377
                        }
378
379
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
380
                            .then(function(recoveryEncryptedSecret) {
381
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
382
                            });
383
                    })
384
                    .then(function() {
385
                        return options;
386
                    });
387
            } else {
388
                return options;
389
            }
390
        });
391
};
392
393
var doRemainingWalletDataV2_3 = function(options, network, notify) {
394
    return q.when(options)
395
        .then(function(options) {
396
            if (!options.backupPublicKey) {
397
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
398
            }
399
400
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
401
402
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
403
404
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
405
406
            if (!options.backupPublicKey) {
407
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
408
                options.backupPublicKey = options.backupPrivateKey.neutered();
409
            }
410
411
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
412
413
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
414
415
            return options;
416
        });
417
};
418
419
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
420
    var self = this;
421
422
    var deferred = q.defer();
423
    deferred.promise.spreadNodeify(cb);
424
425
    deferred.resolve(q.fcall(function() {
426
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
427
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
428
        });
429
    }));
430
431
    return deferred.promise;
432
};
433
434
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
435
    var self = this;
436
437
    if (useWebWorker) {
438
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
439
            return require('./webworker');
440
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
441
            .then(function(data) {
442
                return data.seed;
443
            });
444
    } else {
445
        try {
446
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
447
        } catch (e) {
448
            return q.reject(e);
449
        }
450
    }
451
};
452
453
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
454
    var self = this;
455
456
    var deferred = q.defer();
457
    deferred.promise.nodeify(cb);
458
459
    try {
460
        // avoid conflicting options
461
        if (options.passphrase && options.password) {
462
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
463
        }
464
        // normalize passphrase/password
465
        options.passphrase = options.passphrase || options.password;
466
        delete options.password;
467
468
        // avoid conflicting options
469
        if (options.primaryMnemonic && options.primarySeed) {
470
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
471
        }
472
473
        // avoid deprecated options
474
        if (options.primaryPrivateKey) {
475
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
476
        }
477
478
        // make sure we have at least one thing to use
479
        if (!options.primaryMnemonic && !options.primarySeed) {
480
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
481
        }
482
483
        if (options.primarySeed) {
484
            self.primarySeed = options.primarySeed;
485
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
486
            deferred.resolve(options);
487
        } else {
488
            if (!options.passphrase) {
489
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
490
            }
491
492
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
493
                .then(function(seedHex) {
494
                    try {
495
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
496
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
497
                        deferred.resolve(options);
498
                    } catch (e) {
499
                        deferred.reject(e);
500
                    }
501
                }, function(e) {
502
                    deferred.reject(e);
503
                });
504
        }
505
    } catch (e) {
506
        deferred.reject(e);
507
    }
508
509
    return deferred.promise;
510
};
511
512
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
513
    var self = this;
514
515
    var deferred = q.defer();
516
    deferred.promise.nodeify(cb);
517
518
    try {
519
        // avoid conflicting options
520
        if (options.backupMnemonic && options.backupPublicKey) {
521
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
522
        }
523
524
        // make sure we have at least one thing to use
525
        if (!options.backupMnemonic && !options.backupPublicKey) {
526
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
527
        }
528
529
        if (options.backupPublicKey) {
530
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
531
                deferred.resolve(options);
532
            } else {
533
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
534
                deferred.resolve(options);
535
            }
536
        } else {
537
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
538
                options.backupPublicKey = backupPrivateKey.neutered();
539
                deferred.resolve(options);
540
            }, function(e) {
541
                deferred.reject(e);
542
            });
543
        }
544
    } catch (e) {
545
        deferred.reject(e);
546
    }
547
548
    return deferred.promise;
549
};
550
551
APIClient.prototype.debugAuth = function(cb) {
552
    var self = this;
553
554
    return self.dataClient.get("/debug/http-signature", null, true, cb);
555
};
556
557
/**
558
 * get a single address
559
 *
560
 * @param address      string       address hash
561
 * @param [cb]          function    callback function to call when request is complete
562
 * @return q.Promise
563
 */
564
APIClient.prototype.address = function(address, cb) {
565
    var self = this;
566
567
    return callbackify(self.dataClient.get(self.converter.getUrlForAddress(address), null)
568
        .then(function(data) {
569
            return self.converter.handleErros(self, data);
570
        })
571
        .then(function(data) {
572
            if (data === null) {
573
                return data;
574
            } else {
575
                return self.converter.convertAddress(data);
576
            }
577
        }), cb);
578
};
579
580
APIClient.prototype.addresses = function(addresses, cb) {
581
    var self = this;
582
583
    return callbackify(self.dataClient.post("/address", null, {"addresses": addresses}), cb);
584
};
585
586
587
/**
588
 * get all transactions for an address (paginated)
589
 *
590
 * @param address       string      address hash
591
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
592
 * @param [cb]          function    callback function to call when request is complete
593
 * @return q.Promise
594
 */
595
APIClient.prototype.addressTransactions = function(address, params, cb) {
596
597
    var self = this;
598
599
    if (typeof params === "function") {
600
        cb = params;
601
        params = null;
602
    }
603
604
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
605
        .then(function(data) {
606
            return self.converter.handleErros(self, data);
607
        })
608
        .then(function(data) {
609
            return data.data === null ? data : self.converter.convertAddressTxs(data);
610
        }), cb);
611
};
612
613
/**
614
 * get all transactions for a batch of addresses (paginated)
615
 *
616
 * @param addresses     array       address hashes
617
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
618
 * @param [cb]          function    callback function to call when request is complete
619
 * @return q.Promise
620
 */
621
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
622
    var self = this;
623
624
    if (typeof params === "function") {
625
        cb = params;
626
        params = null;
627
    }
628
629
    return self.dataClient.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
630
};
631
632
/**
633
 * get all unconfirmed transactions for an address (paginated)
634
 *
635
 * @param address       string      address hash
636
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
637
 * @param [cb]          function    callback function to call when request is complete
638
 * @return q.Promise
639
 */
640
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
641
    var self = this;
642
643
    if (typeof params === "function") {
644
        cb = params;
645
        params = null;
646
    }
647
648
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
649
        .then(function(data) {
650
            return self.converter.handleErros(self, data);
651
        })
652
        .then(function(data) {
653
            if (data.data === null) {
654
                return data;
655
            }
656
657
            var res = self.converter.convertAddressTxs(data);
658
            res.data = res.data.filter(function(tx) {
659
                return !tx.confirmations;
660
            });
661
662
            return res;
663
        }), cb);
664
};
665
666
/**
667
 * get all unspent outputs for an address (paginated)
668
 *
669
 * @param address       string      address hash
670
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
671
 * @param [cb]          function    callback function to call when request is complete
672
 * @return q.Promise
673
 */
674
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
675
    var self = this;
676
677
    if (typeof params === "function") {
678
        cb = params;
679
        params = null;
680
    }
681
682
    return callbackify(self.dataClient.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
683
        .then(function(data) {
684
            return self.converter.handleErros(self, data);
685
        })
686
        .then(function(data) {
687
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
688
        }), cb);
689
};
690
691
/**
692
 * get all unspent outputs for a batch of addresses (paginated)
693
 *
694
 * @param addresses     array       address hashes
695
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
696
 * @param [cb]          function    callback function to call when request is complete
697
 * @return q.Promise
698
 */
699
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
700
    var self = this;
701
702
    if (self.converter instanceof BtccomConverter) {
703
        throw new Error("Not implemented");
704
    }
705
706
    if (typeof params === "function") {
707
        cb = params;
708
        params = null;
709
    }
710
711
    return callbackify(self.dataClient.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
712
};
713
714
/**
715
 * verify ownership of an address
716
 *
717
 * @param address       string      address hash
718
 * @param signature     string      a signed message (the address hash) using the private key of the address
719
 * @param [cb]          function    callback function to call when request is complete
720
 * @return q.Promise
721
 */
722
APIClient.prototype.verifyAddress = function(address, signature, cb) {
723
    var self = this;
724
725
    return self.verifyMessage(address, address, signature, cb);
726
};
727
728
/**
729
 *
730
 * get all blocks (paginated)
731
 * ASK
732
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
733
 * @param [cb]          function    callback function to call when request is complete
734
 * @return q.Promise
735
 */
736
APIClient.prototype.allBlocks = function(params, cb) {
737
    var self = this;
738
739
    if (self.converter instanceof BtccomConverter) {
740
        throw new Error("Not implemented");
741
    }
742
743
    if (typeof params === "function") {
744
        cb = params;
745
        params = null;
746
    }
747
748
    return callbackify(self.dataClient.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
749
};
750
751
/**
752
 * get a block
753
 *
754
 * @param block         string|int  a block hash or a block height
755
 * @param [cb]          function    callback function to call when request is complete
756
 * @return q.Promise
757
 */
758
APIClient.prototype.block = function(block, cb) {
759
    var self = this;
760
761
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock(block), null)
762
        .then(function(data) {
763
            return self.converter.handleErros(self, data);
764
        })
765
        .then(function(data) {
766
            return data.data === null ? data : self.converter.convertBlock(data);
767
        }), cb);
768
};
769
770
/**
771
 * get the latest block
772
 *
773
 * @param [cb]          function    callback function to call when request is complete
774
 * @return q.Promise
775
 */
776
APIClient.prototype.blockLatest = function(cb) {
777
    var self = this;
778
779
    return callbackify(self.dataClient.get(self.converter.getUrlForBlock("latest"), null)
780
        .then(function(data) {
781
            return self.converter.handleErros(self, data);
782
        })
783
        .then(function(data) {
784
            return data.data === null ? data : self.converter.convertBlock(data);
785
        }), cb);
786
};
787
788
/**
789
 * get all transactions for a block (paginated)
790
 *
791
 * @param block         string|int  a block hash or a block height
792
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
793
 * @param [cb]          function    callback function to call when request is complete
794
 * @return q.Promise
795
 */
796
APIClient.prototype.blockTransactions = function(block, params, cb) {
797
    var self = this;
798
799
    if (typeof params === "function") {
800
        cb = params;
801
        params = null;
802
    }
803
804
    return callbackify(self.dataClient.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
805
        .then(function(data) {
806
            return self.converter.handleErros(self, data);
807
        })
808
        .then(function(data) {
809
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
810
        }), cb);
811
};
812
813
/**
814
 * get a single transaction
815
 *
816
 * @param tx            string      transaction hash
817
 * @param [cb]          function    callback function to call when request is complete
818
 * @return q.Promise
819
 */
820
APIClient.prototype.transaction = function(tx, cb) {
821
    var self = this;
822
823
    return callbackify(self.dataClient.get(self.converter.getUrlForTransaction(tx), null)
824
        .then(function(data) {
825
            return self.converter.handleErros(self, data);
826
        })
827
        .then(function(data) {
828
            if (data.data === null) {
829
                return data;
830
            } else {
831
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
832
                if (self.converter instanceof BtccomConverter) {
833
                    var txPath = data.data.hash + ".rawhex";
834
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
835
                        .then(function(rawTx) {
836
                            return [data, rawTx];
837
                        })
838
                        .then(function(dataAndTx) {
839
                            if (dataAndTx !== null) {
840
                                var data = dataAndTx[0];
841
                                var rawTx = dataAndTx[1];
842
                                return self.converter.convertTx(data, rawTx);
843
                            } else {
844
                                return dataAndTx;
845
                            }
846
                        });
847
                } else {
848
                    return self.converter.convertTx(data);
849
                }
850
            }
851
        }), cb);
852
};
853
854
/**
855
 * get a batch of transactions
856
 *
857
 * @param txs           string[]    list of transaction hashes (txId)
858
 * @param [cb]          function    callback function to call when request is complete
859
 * @return q.Promise
860
 */
861
APIClient.prototype.transactions = function(txs, cb) {
862
    var self = this;
863
864
    if (self.converter instanceof BtccomConverter) {
865
        return callbackify(self.dataClient.get(self.converter.getUrlForTransactions(txs), null)
866
            .then(function(data) {
867
                return self.converter.handleErros(self, data);
868
            })
869
            .then(function(data) {
870
                if (data.data === null) {
871
                    return data;
872
                } else {
873
                    return self.converter.convertTxs(data);
874
                }
875
            }), cb);
876
    } else {
877
        return callbackify(self.dataClient.post("/transactions", null, txs, null, false), cb);
878
    }
879
};
880
881
/**
882
 * get a paginated list of all webhooks associated with the api user
883
 *
884
 * @param [params]      object      pagination: {page: 1, limit: 20}
885
 * @param [cb]          function    callback function to call when request is complete
886
 * @return q.Promise
887
 */
888
APIClient.prototype.allWebhooks = function(params, cb) {
889
    var self = this;
890
891
    if (typeof params === "function") {
892
        cb = params;
893
        params = null;
894
    }
895
896
    return self.blocktrailClient.get("/webhooks", params, cb);
897
};
898
899
/**
900
 * create a new webhook
901
 *
902
 * @param url           string      the url to receive the webhook events
903
 * @param [identifier]  string      a unique identifier associated with the webhook
904
 * @param [cb]          function    callback function to call when request is complete
905
 * @return q.Promise
906
 */
907
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
908
    var self = this;
909
910
    if (typeof identifier === "function") {
911
        //mimic function overloading
912
        cb = identifier;
913
        identifier = null;
914
    }
915
916
    return self.blocktrailClient.post("/webhook", null, {url: url, identifier: identifier}, cb);
917
};
918
919
/**
920
 * Converts a cash address to the legacy (base58) format
921
 * @param {string} input
922
 * @returns {string}
923
 */
924
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
925
    if (this.network === bitcoin.networks.bitcoincash ||
926
        this.network === bitcoin.networks.bitcoincashtestnet ||
927
        this.network === bitcoin.networks.bitcoincashregtest) {
928
        var address;
929
        try {
930
            bitcoin.address.fromBase58Check(input, this.network);
931
            return input;
932
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
933
934
        address = bitcoin.address.fromCashAddress(input, this.network);
935
        var prefix;
936
        if (address.version === bitcoin.script.types.P2PKH) {
937
            prefix = this.network.pubKeyHash;
938
        } else if (address.version === bitcoin.script.types.P2SH) {
939
            prefix = this.network.scriptHash;
940
        } else {
941
            throw new Error("Unsupported address type");
942
        }
943
944
        return bitcoin.address.toBase58Check(address.hash, prefix);
945
    }
946
947
    throw new Error("Cash addresses only work on bitcoin cash");
948
};
949
950
/**
951
 * Converts a legacy bitcoin to the new cashaddr format
952
 * @param {string} input
953
 * @returns {string}
954
 */
955
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
956
    if (this.network === bitcoin.networks.bitcoincash ||
957
        this.network === bitcoin.networks.bitcoincashtestnet ||
958
        this.network === bitcoin.networks.bitcoincashregtest
959
    ) {
960
        var address;
961
        try {
962
            bitcoin.address.fromCashAddress(input, this.network);
963
            return input;
964
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
965
966
        address = bitcoin.address.fromBase58Check(input, this.network);
967
        var scriptType;
968
        if (address.version === this.network.pubKeyHash) {
969
            scriptType = bitcoin.script.types.P2PKH;
970
        } else if (address.version === this.network.scriptHash) {
971
            scriptType = bitcoin.script.types.P2SH;
972
        } else {
973
            throw new Error("Unsupported address type");
974
        }
975
976
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
977
    }
978
979
    throw new Error("Cash addresses only work on bitcoin cash");
980
};
981
982
/**
983
 * get an existing webhook by it's identifier
984
 *
985
 * @param identifier    string      the unique identifier of the webhook to get
986
 * @param [cb]          function    callback function to call when request is complete
987
 * @return q.Promise
988
 */
989
APIClient.prototype.getWebhook = function(identifier, cb) {
990
    var self = this;
991
992
    return self.blocktrailClient.get("/webhook/" + identifier, null, cb);
993
};
994
995
/**
996
 * update an existing webhook
997
 *
998
 * @param identifier    string      the unique identifier of the webhook
999
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
1000
 * @param [cb]          function    callback function to call when request is complete
1001
 * @return q.Promise
1002
 */
1003
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
1004
    var self = this;
1005
1006
    return self.blocktrailClient.put("/webhook/" + identifier, null, webhookData, cb);
1007
};
1008
1009
/**
1010
 * deletes an existing webhook and any event subscriptions associated with it
1011
 *
1012
 * @param identifier    string      the unique identifier of the webhook
1013
 * @param [cb]          function    callback function to call when request is complete
1014
 * @return q.Promise
1015
 */
1016
APIClient.prototype.deleteWebhook = function(identifier, cb) {
1017
    var self = this;
1018
1019
    return self.blocktrailClient.delete("/webhook/" + identifier, null, null, cb);
1020
};
1021
1022
/**
1023
 * get a paginated list of all the events a webhook is subscribed to
1024
 *
1025
 * @param identifier    string      the unique identifier of the webhook
1026
 * @param [params]      object      pagination: {page: 1, limit: 20}
1027
 * @param [cb]          function    callback function to call when request is complete
1028
 * @return q.Promise
1029
 */
1030
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
1031
    var self = this;
1032
1033
    if (typeof params === "function") {
1034
        cb = params;
1035
        params = null;
1036
    }
1037
1038
    return self.blocktrailClient.get("/webhook/" + identifier + "/events", params, cb);
1039
};
1040
1041
/**
1042
 * subscribes a webhook to transaction events for a particular transaction
1043
 *
1044
 * @param identifier    string      the unique identifier of the webhook
1045
 * @param transaction   string      the transaction hash
1046
 * @param confirmations integer     the amount of confirmations to send
1047
 * @param [cb]          function    callback function to call when request is complete
1048
 * @return q.Promise
1049
 */
1050
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
1051
    var self = this;
1052
    var postData = {
1053
        'event_type': 'transaction',
1054
        'transaction': transaction,
1055
        'confirmations': confirmations
1056
    };
1057
1058
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1059
};
1060
1061
/**
1062
 * subscribes a webhook to transaction events on a particular address
1063
 *
1064
 * @param identifier    string      the unique identifier of the webhook
1065
 * @param address       string      the address hash
1066
 * @param confirmations integer     the amount of confirmations to send
1067
 * @param [cb]          function    callback function to call when request is complete
1068
 * @return q.Promise
1069
 */
1070
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
1071
    var self = this;
1072
    var postData = {
1073
        'event_type': 'address-transactions',
1074
        'address': address,
1075
        'confirmations': confirmations
1076
    };
1077
1078
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1079
};
1080
1081
/**
1082
 * batch subscribes a webhook to multiple transaction events
1083
 *
1084
 * @param  identifier   string      the unique identifier of the webhook
1085
 * @param  batchData    array       An array of objects containing batch event data:
1086
 *                                  {address : 'address', confirmations : 'confirmations']
1087
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1088
 * @param [cb]          function    callback function to call when request is complete
1089
 * @return q.Promise
1090
 */
1091
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1092
    var self = this;
1093
    batchData.forEach(function(record) {
1094
        record.event_type = 'address-transactions';
1095
    });
1096
1097
    return self.blocktrailClient.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1098
};
1099
1100
/**
1101
 * subscribes a webhook to a new block event
1102
 *
1103
 * @param identifier    string      the unique identifier of the webhook
1104
 * @param [cb]          function    callback function to call when request is complete
1105
 * @return q.Promise
1106
 */
1107
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1108
    var self = this;
1109
    var postData = {
1110
        'event_type': 'block'
1111
    };
1112
1113
    return self.blocktrailClient.post("/webhook/" + identifier + "/events", null, postData, cb);
1114
};
1115
1116
/**
1117
 * removes an address transaction event subscription from a webhook
1118
 *
1119
 * @param identifier    string      the unique identifier of the webhook
1120
 * @param address       string      the address hash
1121
 * @param [cb]          function    callback function to call when request is complete
1122
 * @return q.Promise
1123
 */
1124
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1125
    var self = this;
1126
1127
    return self.blocktrailClient.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1128
};
1129
1130
/**
1131
 * removes an transaction event subscription from a webhook
1132
 *
1133
 * @param identifier    string      the unique identifier of the webhook
1134
 * @param transaction   string      the transaction hash
1135
 * @param [cb]          function    callback function to call when request is complete
1136
 * @return q.Promise
1137
 */
1138
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1139
    var self = this;
1140
1141
    return self.blocktrailClient.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1142
};
1143
1144
/**
1145
 * removes a block event subscription from a webhook
1146
 *
1147
 * @param identifier    string      the unique identifier of the webhook
1148
 * @param [cb]          function    callback function to call when request is complete
1149
 * @return q.Promise
1150
 */
1151
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1152
    var self = this;
1153
1154
    return self.blocktrailClient.delete("/webhook/" + identifier + "/block", null, null, cb);
1155
};
1156
1157
/**
1158
 * initialize an existing wallet
1159
 *
1160
 * Either takes two argument:
1161
 * @param options       object      {}
1162
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1163
 *
1164
 * Or takes three arguments (old, deprecated syntax):
1165
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1166
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1167
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1162. The second definition is ignored.
Loading history...
1168
 *
1169
 * @returns {q.Promise}
1170
 */
1171
APIClient.prototype.initWallet = function(options, cb) {
1172
    var self = this;
1173
1174
    if (typeof options !== "object") {
1175
        // get the old-style arguments
1176
        options = {
1177
            identifier: arguments[0],
1178
            passphrase: arguments[1]
1179
        };
1180
1181
        cb = arguments[2];
1182
    }
1183
1184
    if (options.check_backup_key) {
1185
        if (typeof options.check_backup_key !== "string") {
1186
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1187
        }
1188
    }
1189
1190
    var deferred = q.defer();
1191
    deferred.promise.spreadNodeify(cb);
1192
1193
    var identifier = options.identifier;
1194
1195
    if (!identifier) {
1196
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1197
        return deferred.promise;
1198
    }
1199
1200
    deferred.resolve(self.blocktrailClient.get("/wallet/" + identifier, null, true).then(function(result) {
1201
        var keyIndex = options.keyIndex || result.key_index;
1202
1203
        options.walletVersion = result.wallet_version;
1204
1205
        if (options.check_backup_key) {
1206
            if (options.check_backup_key !== result.backup_public_key[0]) {
1207
                throw new Error("Backup key returned from server didn't match our own copy");
1208
            }
1209
        }
1210
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1211
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1212
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1213
        });
1214
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1215
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1216
        });
1217
1218
        // initialize wallet
1219
        var wallet = new Wallet(
1220
            self,
1221
            identifier,
1222
            options.walletVersion,
1223
            result.primary_mnemonic,
1224
            result.encrypted_primary_seed,
1225
            result.encrypted_secret,
1226
            primaryPublicKeys,
1227
            backupPublicKey,
1228
            blocktrailPublicKeys,
1229
            keyIndex,
1230
            result.segwit || 0,
1231
            self.testnet,
1232
            self.regtest,
1233
            result.checksum,
1234
            result.upgrade_key_index,
1235
            options.useCashAddress,
1236
            options.bypassNewAddressCheck
1237
        );
1238
1239
        wallet.recoverySecret = result.recovery_secret;
1240
1241
        if (!options.readOnly) {
1242
            return wallet.unlock(options).then(function() {
1243
                return wallet;
1244
            });
1245
        } else {
1246
            return wallet;
1247
        }
1248
    }));
1249
1250
    return deferred.promise;
1251
};
1252
1253
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1254
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1255
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1256
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1257
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1258
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1259
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1260
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1261
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1262
1263
/**
1264
 * create a new wallet
1265
 *   - will generate a new primary seed and backup seed
1266
 *
1267
 * Either takes two argument:
1268
 * @param options       object      {}
1269
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1270
 *
1271
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1272
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1268. The second definition is ignored.
Loading history...
1273
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1269. The second definition is ignored.
Loading history...
1274
 *
1275
 * Or takes four arguments (old, deprecated syntax):
1276
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1277
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1278
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1279
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1269. The second definition is ignored.
Loading history...
1280
 * @returns {q.Promise}
1281
 */
1282
APIClient.prototype.createNewWallet = function(options, cb) {
1283
    /* jshint -W071, -W074 */
1284
1285
    var self = this;
1286
1287
    if (typeof options !== "object") {
1288
        // get the old-style arguments
1289
        var identifier = arguments[0];
1290
        var passphrase = arguments[1];
1291
        var keyIndex = arguments[2];
1292
        cb = arguments[3];
1293
1294
        // keyIndex is optional
1295
        if (typeof keyIndex === "function") {
1296
            cb = keyIndex;
1297
            keyIndex = null;
1298
        }
1299
1300
        options = {
1301
            identifier: identifier,
1302
            passphrase: passphrase,
1303
            keyIndex: keyIndex
1304
        };
1305
    }
1306
1307
    // default to v3
1308
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1309
1310
    var deferred = q.defer();
1311
    deferred.promise.spreadNodeify(cb);
1312
1313
    q.nextTick(function() {
1314
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1315
1316
        options.keyIndex = options.keyIndex || 0;
1317
        options.passphrase = options.passphrase || options.password;
1318
        delete options.password;
1319
1320
        if (!options.identifier) {
1321
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1322
            return deferred.promise;
1323
        }
1324
1325
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1326
            self._createNewWalletV1(options)
1327
                .progress(function(p) { deferred.notify(p); })
1328
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1329
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1330
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1331
            self._createNewWalletV2(options)
1332
                .progress(function(p) { deferred.notify(p); })
1333
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1334
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1335
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1336
            self._createNewWalletV3(options)
1337
                .progress(function(p) { deferred.notify(p); })
1338
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1339
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1340
        } else {
1341
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1342
        }
1343
    });
1344
1345
    return deferred.promise;
1346
};
1347
1348
APIClient.prototype._createNewWalletV1 = function(options) {
1349
    var self = this;
1350
1351
    var deferred = q.defer();
1352
1353
    q.nextTick(function() {
1354
1355
        if (!options.primaryMnemonic && !options.primarySeed) {
1356
            if (!options.passphrase && !options.password) {
1357
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1358
                return deferred.promise;
1359
            } else {
1360
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1361
                if (options.storePrimaryMnemonic !== false) {
1362
                    options.storePrimaryMnemonic = true;
1363
                }
1364
            }
1365
        }
1366
1367
        if (!options.backupMnemonic && !options.backupPublicKey) {
1368
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1369
        }
1370
1371
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1372
1373
        self.resolvePrimaryPrivateKeyFromOptions(options)
1374
            .then(function(options) {
1375
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1376
1377
                return self.resolveBackupPublicKeyFromOptions(options)
1378
                    .then(function(options) {
1379
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1380
1381
                        // create a checksum of our private key which we'll later use to verify we used the right password
1382
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1383
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1384
                        var keyIndex = options.keyIndex;
1385
1386
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1387
1388
                        // send the public keys to the server to store them
1389
                        //  and the mnemonic, which is safe because it's useless without the password
1390
                        return self.storeNewWalletV1(
1391
                            options.identifier,
1392
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1393
                            [options.backupPublicKey.toBase58(), "M"],
1394
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1395
                            checksum,
1396
                            keyIndex,
1397
                            options.segwit || null
1398
                        )
1399
                            .then(function(result) {
1400
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1401
1402
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1403
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1404
                                });
1405
1406
                                var wallet = new Wallet(
1407
                                    self,
1408
                                    options.identifier,
1409
                                    Wallet.WALLET_VERSION_V1,
1410
                                    options.primaryMnemonic,
1411
                                    null,
1412
                                    null,
1413
                                    {keyIndex: primaryPublicKey},
1414
                                    options.backupPublicKey,
1415
                                    blocktrailPublicKeys,
1416
                                    keyIndex,
1417
                                    result.segwit || 0,
1418
                                    self.testnet,
1419
                                    self.regtest,
1420
                                    checksum,
1421
                                    result.upgrade_key_index,
1422
                                    options.useCashAddress,
1423
                                    options.bypassNewAddressCheck
1424
                                );
1425
1426
                                return wallet.unlock({
1427
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1428
                                    passphrase: options.passphrase,
1429
                                    primarySeed: options.primarySeed,
1430
                                    primaryMnemonic: null // explicit null
1431
                                }).then(function() {
1432
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1433
                                    return [
1434
                                        wallet,
1435
                                        {
1436
                                            walletVersion: wallet.walletVersion,
1437
                                            primaryMnemonic: options.primaryMnemonic,
1438
                                            backupMnemonic: options.backupMnemonic,
1439
                                            blocktrailPublicKeys: blocktrailPublicKeys
1440
                                        }
1441
                                    ];
1442
                                });
1443
                            });
1444
                    }
1445
                );
1446
            })
1447
            .then(
1448
            function(r) {
1449
                deferred.resolve(r);
1450
            },
1451
            function(e) {
1452
                deferred.reject(e);
1453
            }
1454
        )
1455
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1456
    });
1457
1458
    return deferred.promise;
1459
};
1460
1461
APIClient.prototype._createNewWalletV2 = function(options) {
1462
    var self = this;
1463
1464
    var deferred = q.defer();
1465
1466
    // avoid modifying passed options
1467
    options = _.merge({}, options);
1468
1469
    determineDataStorageV2_3(options)
1470
        .then(function(options) {
1471
            options.passphrase = options.passphrase || options.password;
1472
            delete options.password;
1473
1474
            // avoid deprecated options
1475
            if (options.primaryPrivateKey) {
1476
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1477
            }
1478
1479
            // seed should be provided or generated
1480
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1481
1482
            return options;
1483
        })
1484
        .then(function(options) {
1485
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1486
        })
1487
        .then(function(options) {
1488
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1489
        })
1490 View Code Duplication
        .then(function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1491
            // create a checksum of our private key which we'll later use to verify we used the right password
1492
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1493
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1494
            var keyIndex = options.keyIndex;
1495
1496
            // send the public keys and encrypted data to server
1497
            return self.storeNewWalletV2(
1498
                options.identifier,
1499
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1500
                [options.backupPublicKey.toBase58(), "M"],
1501
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1502
                options.storeDataOnServer ? options.encryptedSecret : false,
1503
                options.storeDataOnServer ? options.recoverySecret : false,
1504
                checksum,
1505
                keyIndex,
1506
                options.support_secret || null,
1507
                options.segwit || null
1508
            )
1509
                .then(
1510
                function(result) {
1511
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1512
1513
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1514
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1515
                    });
1516
1517
                    var wallet = new Wallet(
1518
                        self,
1519
                        options.identifier,
1520
                        Wallet.WALLET_VERSION_V2,
1521
                        null,
1522
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1523
                        options.storeDataOnServer ? options.encryptedSecret : null,
1524
                        {keyIndex: options.primaryPublicKey},
1525
                        options.backupPublicKey,
1526
                        blocktrailPublicKeys,
1527
                        keyIndex,
1528
                        result.segwit || 0,
1529
                        self.testnet,
1530
                        self.regtest,
1531
                        checksum,
1532
                        result.upgrade_key_index,
1533
                        options.useCashAddress,
1534
                        options.bypassNewAddressCheck
1535
                    );
1536
1537
                    // pass along decrypted data to avoid extra work
1538
                    return wallet.unlock({
1539
                        walletVersion: Wallet.WALLET_VERSION_V2,
1540
                        passphrase: options.passphrase,
1541
                        primarySeed: options.primarySeed,
1542
                        secret: options.secret
1543
                    }).then(function() {
1544
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1545
                        return [
1546
                            wallet,
1547
                            {
1548
                                walletVersion: wallet.walletVersion,
1549
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1550
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1551
                                    null,
1552
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1553
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1554
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1555
                                    null,
1556
                                encryptedSecret: options.encryptedSecret ?
1557
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1558
                                    null,
1559
                                blocktrailPublicKeys: blocktrailPublicKeys
1560
                            }
1561
                        ];
1562
                    });
1563
                }
1564
            );
1565
        })
1566
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1567
1568
    return deferred.promise;
1569
};
1570
1571
APIClient.prototype._createNewWalletV3 = function(options) {
1572
    var self = this;
1573
1574
    var deferred = q.defer();
1575
1576
    // avoid modifying passed options
1577
    options = _.merge({}, options);
1578
1579
    determineDataStorageV2_3(options)
1580
        .then(function(options) {
1581
            options.passphrase = options.passphrase || options.password;
1582
            delete options.password;
1583
1584
            // avoid deprecated options
1585
            if (options.primaryPrivateKey) {
1586
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1587
            }
1588
1589
            // seed should be provided or generated
1590
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1591
1592
            return options;
1593
        })
1594
        .then(function(options) {
1595
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1596
        })
1597
        .then(function(options) {
1598
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1599
        })
1600 View Code Duplication
        .then(function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1601
            // create a checksum of our private key which we'll later use to verify we used the right password
1602
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1603
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1604
            var keyIndex = options.keyIndex;
1605
1606
            // send the public keys and encrypted data to server
1607
            return self.storeNewWalletV3(
1608
                options.identifier,
1609
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1610
                [options.backupPublicKey.toBase58(), "M"],
1611
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1612
                options.storeDataOnServer ? options.encryptedSecret : false,
1613
                options.storeDataOnServer ? options.recoverySecret : false,
1614
                checksum,
1615
                keyIndex,
1616
                options.support_secret || null,
1617
                options.segwit || null
1618
            )
1619
                .then(
1620
                    // result, deferred, self(apiclient)
1621
                    function(result) {
1622
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1623
1624
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1625
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1626
                        });
1627
1628
                        var wallet = new Wallet(
1629
                            self,
1630
                            options.identifier,
1631
                            Wallet.WALLET_VERSION_V3,
1632
                            null,
1633
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1634
                            options.storeDataOnServer ? options.encryptedSecret : null,
1635
                            {keyIndex: options.primaryPublicKey},
1636
                            options.backupPublicKey,
1637
                            blocktrailPublicKeys,
1638
                            keyIndex,
1639
                            result.segwit || 0,
1640
                            self.testnet,
1641
                            self.regtest,
1642
                            checksum,
1643
                            result.upgrade_key_index,
1644
                            options.useCashAddress,
1645
                            options.bypassNewAddressCheck
1646
                        );
1647
1648
                        // pass along decrypted data to avoid extra work
1649
                        return wallet.unlock({
1650
                            walletVersion: Wallet.WALLET_VERSION_V3,
1651
                            passphrase: options.passphrase,
1652
                            primarySeed: options.primarySeed,
1653
                            secret: options.secret
1654
                        }).then(function() {
1655
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1656
                            return [
1657
                                wallet,
1658
                                {
1659
                                    walletVersion: wallet.walletVersion,
1660
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1661
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1662
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1663
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1664
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1665
                                    blocktrailPublicKeys: blocktrailPublicKeys
1666
                                }
1667
                            ];
1668
                        });
1669
                    }
1670
                );
1671
        })
1672
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1673
1674
    return deferred.promise;
1675
};
1676
1677
function verifyPublicBip32Key(bip32Key, network) {
1678
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1679
    if (typeof hk.keyPair.d !== "undefined") {
1680
        throw new Error('BIP32Key contained private key material - abort');
1681
    }
1682
1683
    if (bip32Key[1].slice(0, 1) !== "M") {
1684
        throw new Error("BIP32Key contained non-public path - abort");
1685
    }
1686
}
1687
1688
function verifyPublicOnly(walletData, network) {
1689
    verifyPublicBip32Key(walletData.primary_public_key, network);
1690
    verifyPublicBip32Key(walletData.backup_public_key, network);
1691
}
1692
1693
/**
1694
 * create wallet using the API
1695
 *
1696
 * @param identifier            string      the wallet identifier to create
1697
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1698
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1699
 * @param primaryMnemonic       string      mnemonic to store
1700
 * @param checksum              string      checksum to store
1701
 * @param keyIndex              int         keyIndex that was used to create wallet
1702
 * @param segwit                bool
1703
 * @returns {q.Promise}
1704
 */
1705
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1706
                                                checksum, keyIndex, segwit) {
1707
    var self = this;
1708
1709
    var postData = {
1710
        identifier: identifier,
1711
        wallet_version: Wallet.WALLET_VERSION_V1,
1712
        primary_public_key: primaryPublicKey,
1713
        backup_public_key: backupPublicKey,
1714
        primary_mnemonic: primaryMnemonic,
1715
        checksum: checksum,
1716
        key_index: keyIndex,
1717
        segwit: segwit
1718
    };
1719
1720
    verifyPublicOnly(postData, self.network);
1721
1722
    return self.blocktrailClient.post("/wallet", null, postData);
1723
};
1724
1725
/**
1726
 * create wallet using the API
1727
 *
1728
 * @param identifier            string      the wallet identifier to create
1729
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1730
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1731
 * @param encryptedPrimarySeed  string      openssl format
1732
 * @param encryptedSecret       string      openssl format
1733
 * @param recoverySecret        string      openssl format
1734
 * @param checksum              string      checksum to store
1735
 * @param keyIndex              int         keyIndex that was used to create wallet
1736
 * @param supportSecret         string
1737
 * @param segwit                bool
1738
 * @returns {q.Promise}
1739
 */
1740
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1741
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1742
    var self = this;
1743
1744
    var postData = {
1745
        identifier: identifier,
1746
        wallet_version: Wallet.WALLET_VERSION_V2,
1747
        primary_public_key: primaryPublicKey,
1748
        backup_public_key: backupPublicKey,
1749
        encrypted_primary_seed: encryptedPrimarySeed,
1750
        encrypted_secret: encryptedSecret,
1751
        recovery_secret: recoverySecret,
1752
        checksum: checksum,
1753
        key_index: keyIndex,
1754
        support_secret: supportSecret || null,
1755
        segwit: segwit
1756
    };
1757
1758
    verifyPublicOnly(postData, self.network);
1759
1760
    return self.blocktrailClient.post("/wallet", null, postData);
1761
};
1762
1763
/**
1764
 * create wallet using the API
1765
 *
1766
 * @param identifier            string      the wallet identifier to create
1767
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1768
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1769
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1770
 * @param encryptedSecret       Buffer      buffer of ciphertext
1771
 * @param recoverySecret        Buffer      buffer of recovery secret
1772
 * @param checksum              string      checksum to store
1773
 * @param keyIndex              int         keyIndex that was used to create wallet
1774
 * @param supportSecret         string
1775
 * @param segwit                bool
1776
 * @returns {q.Promise}
1777
 */
1778
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1779
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1780
    var self = this;
1781
1782
    var postData = {
1783
        identifier: identifier,
1784
        wallet_version: Wallet.WALLET_VERSION_V3,
1785
        primary_public_key: primaryPublicKey,
1786
        backup_public_key: backupPublicKey,
1787
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1788
        encrypted_secret: encryptedSecret.toString('base64'),
1789
        recovery_secret: recoverySecret.toString('hex'),
1790
        checksum: checksum,
1791
        key_index: keyIndex,
1792
        support_secret: supportSecret || null,
1793
        segwit: segwit
1794
    };
1795
1796
    verifyPublicOnly(postData, self.network);
1797
1798
    return self.blocktrailClient.post("/wallet", null, postData);
1799
};
1800
1801
/**
1802
 * create wallet using the API
1803
 *
1804
 * @param identifier            string      the wallet identifier to create
1805
 * @param postData              object
1806
 * @param [cb]                  function    callback(err, result)
1807
 * @returns {q.Promise}
1808
 */
1809
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1810
    var self = this;
1811
1812
    return self.blocktrailClient.post("/wallet/" + identifier, null, postData, cb);
1813
};
1814
1815
/**
1816
 * upgrade wallet to use a new account number
1817
 *  the account number specifies which blocktrail cosigning key is used
1818
 *
1819
 * @param identifier            string      the wallet identifier
1820
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1821
 * @param keyIndex              int         keyIndex that was used to create wallet
1822
 * @param [cb]                  function    callback(err, result)
1823
 * @returns {q.Promise}
1824
 */
1825
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1826
    var self = this;
1827
1828
    return self.blocktrailClient.post("/wallet/" + identifier + "/upgrade", null, {
1829
        key_index: keyIndex,
1830
        primary_public_key: primaryPublicKey
1831
    }, cb);
1832
};
1833
1834
/**
1835
 * get the balance for the wallet
1836
 *
1837
 * @param identifier            string      the wallet identifier
1838
 * @param [cb]                  function    callback(err, result)
1839
 * @returns {q.Promise}
1840
 */
1841
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1842
    var self = this;
1843
1844
    return self.blocktrailClient.get("/wallet/" + identifier + "/balance", null, true, cb);
1845
};
1846
1847
/**
1848
 * do HD wallet discovery for the wallet
1849
 *
1850
 * @param identifier            string      the wallet identifier
1851
 * @param [cb]                  function    callback(err, result)
1852
 * @returns {q.Promise}
1853
 */
1854
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1855
    var self = this;
1856
1857
    return self.blocktrailClient.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1858
};
1859
1860
1861
/**
1862
 * get a new derivation number for specified parent path
1863
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1864
 *
1865
 * @param identifier            string      the wallet identifier
1866
 * @param path                  string      the parent path for which to get a new derivation,
1867
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1868
 * @param [cb]                  function    callback(err, result)
1869
 * @returns {q.Promise}
1870
 */
1871
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1872
    var self = this;
1873
1874
    return self.blocktrailClient.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1875
};
1876
1877
1878
/**
1879
 * delete the wallet
1880
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1881
 *  is required to be able to delete a wallet
1882
 *
1883
 * @param identifier            string      the wallet identifier
1884
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1885
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1886
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1887
 * @param [cb]                  function    callback(err, result)
1888
 * @returns {q.Promise}
1889
 */
1890
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1891
    var self = this;
1892
1893
    if (typeof force === "function") {
1894
        cb = force;
1895
        force = false;
1896
    }
1897
1898
    return self.blocktrailClient.delete("/wallet/" + identifier, {force: force}, {
1899
        checksum: checksumAddress,
1900
        signature: checksumSignature
1901
    }, cb);
1902
};
1903
1904
/**
1905
 * use the API to get the best inputs to use based on the outputs
1906
 *
1907
 * the return array has the following format:
1908
 * [
1909
 *  "utxos" => [
1910
 *      [
1911
 *          "hash" => "<txHash>",
1912
 *          "idx" => "<index of the output of that <txHash>",
1913
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1914
 *          "value" => 32746327,
1915
 *          "address" => "1address",
1916
 *          "path" => "m/44'/1'/0'/0/13",
1917
 *          "redeem_script" => "<redeemScript-hex>",
1918
 *      ],
1919
 *  ],
1920
 *  "fee"   => 10000,
1921
 *  "change"=> 1010109201,
1922
 * ]
1923
 *
1924
 * @param identifier        string      the wallet identifier
1925
 * @param pay               array       {'address': (int)value}     coins to send
1926
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1927
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1928
 * @param feeStrategy       string      defaults to
1929
 * @param options
1930
 * @param [cb]              function    callback(err, utxos, fee, change)
1931
 * @returns {q.Promise}
1932
 */
1933
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1934
    var self = this;
1935
1936
    if (typeof feeStrategy === "function") {
1937
        cb = feeStrategy;
1938
        feeStrategy = null;
1939
        options = {};
1940
    } else if (typeof options === "function") {
1941
        cb = options;
1942
        options = {};
1943
    }
1944
1945
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1946
    options = options || {};
1947
1948
    var deferred = q.defer();
1949
    deferred.promise.spreadNodeify(cb);
1950
1951
    var params = {
1952
        lock: lockUTXO,
1953
        zeroconf: allowZeroConf ? 1 : 0,
1954
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1955
        fee_strategy: feeStrategy
1956
    };
1957
1958
    if (options.forcefee) {
1959
        params['forcefee'] = options.forcefee;
1960
    }
1961
1962
    deferred.resolve(
1963
        self.blocktrailClient.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1964
            function(result) {
1965
                return [result.utxos, result.fee, result.change, result];
1966
            },
1967
            function(err) {
1968
                if (err.message.match(/too low to pay the fee/)) {
1969
                    throw blocktrail.WalletFeeError(err);
1970
                }
1971
1972
                throw err;
1973
            }
1974
        )
1975
    );
1976
1977
    return deferred.promise;
1978
};
1979
1980
/**
1981
 * @param [cb]              function    callback(err, utxos, fee, change)
1982
 * @returns {q.Promise}
1983
 */
1984
APIClient.prototype.feePerKB = function(cb) {
1985
    var self = this;
1986
1987
    var deferred = q.defer();
1988
    deferred.promise.spreadNodeify(cb);
1989
1990
    deferred.resolve(self.blocktrailClient.get("/fee-per-kb"));
1991
1992
    return deferred.promise;
1993
};
1994
1995
/**
1996
 * send the transaction using the API
1997
 *
1998
 * @param identifier        string      the wallet identifier
1999
 * @param txHex             string      partially signed transaction as hex string
2000
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
2001
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
2002
 * @param [twoFactorToken]  string      2FA token
2003
 * @param [prioboost]       bool
2004
 * @param [cb]              function    callback(err, txHash)
2005
 * @returns {q.Promise}
2006
 */
2007
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
2008
    var self = this;
2009
2010
    if (typeof twoFactorToken === "function") {
2011
        cb = twoFactorToken;
2012
        twoFactorToken = null;
2013
        prioboost = false;
2014
    } else if (typeof prioboost === "function") {
2015
        cb = prioboost;
2016
        prioboost = false;
2017
    }
2018
2019
    var data = {
2020
        paths: paths,
2021
        two_factor_token: twoFactorToken
2022
    };
2023
    if (typeof txHex === "string") {
2024
        data.raw_transaction = txHex;
2025
    } else if (typeof txHex === "object") {
2026
        Object.keys(txHex).map(function(key) {
2027
            data[key] = txHex[key];
2028
        });
2029
    }
2030
2031
    return self.blocktrailClient.post(
2032
        "/wallet/" + identifier + "/send",
2033
        {
2034
            check_fee: checkFee ? 1 : 0,
2035
            prioboost: prioboost ? 1 : 0
2036
        },
2037
        data,
2038
        cb
2039
    );
2040
};
2041
2042
/**
2043
 * setup a webhook for this wallet
2044
 *
2045
 * @param identifier        string      the wallet identifier
2046
 * @param webhookIdentifier string      identifier for the webhook
2047
 * @param url               string      URL to receive webhook events
2048
 * @param [cb]              function    callback(err, webhook)
2049
 * @returns {q.Promise}
2050
 */
2051
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
2052
    var self = this;
2053
2054
    return self.blocktrailClient.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
2055
};
2056
2057
/**
2058
 * delete a webhook that was created for this wallet
2059
 *
2060
 * @param identifier        string      the wallet identifier
2061
 * @param webhookIdentifier string      identifier for the webhook
2062
 * @param [cb]              function    callback(err, success)
2063
 * @returns {q.Promise}
2064
 */
2065
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
2066
    var self = this;
2067
2068
    return self.blocktrailClient.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
2069
};
2070
2071
/**
2072
 * get all transactions for an wallet (paginated)
2073
 *
2074
 * @param identifier    string      wallet identifier
2075
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2076
 * @param [cb]          function    callback function to call when request is complete
2077
 * @return q.Promise
2078
 */
2079
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
2080
    var self = this;
2081
2082
    if (typeof params === "function") {
2083
        cb = params;
2084
        params = null;
2085
    }
2086
2087
    return self.blocktrailClient.get("/wallet/" + identifier + "/transactions", params, true, cb);
2088
};
2089
2090
/**
2091
 * get all addresses for an wallet (paginated)
2092
 *
2093
 * @param identifier    string      wallet identifier
2094
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2095
 * @param [cb]          function    callback function to call when request is complete
2096
 * @return q.Promise
2097
 */
2098
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2099
    var self = this;
2100
2101
    if (typeof params === "function") {
2102
        cb = params;
2103
        params = null;
2104
    }
2105
2106
    return self.blocktrailClient.get("/wallet/" + identifier + "/addresses", params, true, cb);
2107
};
2108
2109
/**
2110
 * @param identifier    string      wallet identifier
2111
 * @param address       string      the address to label
2112
 * @param label         string      the label
2113
 * @param [cb]          function    callback(err, res)
2114
 * @return q.Promise
2115
 */
2116
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2117
    var self = this;
2118
2119
    return self.blocktrailClient.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2120
};
2121
2122
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2123
    var self = this;
2124
2125
    if (typeof feeStrategy === "function") {
2126
        cb = feeStrategy;
2127
        feeStrategy = null;
2128
    } else if (typeof options === "function") {
2129
        cb = options;
2130
        options = {};
2131
    }
2132
2133
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2134
    options = options || {};
2135
2136
    var params = {
2137
        outputs: options.outputs ? options.outputs : 1,
2138
        zeroconf: allowZeroConf ? 1 : 0,
2139
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2140
        fee_strategy: feeStrategy
2141
    };
2142
2143
    if (options.forcefee) {
2144
        params['forcefee'] = options.forcefee;
2145
    }
2146
2147
    return self.blocktrailClient.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2148
};
2149
2150
/**
2151
 * get all UTXOs for an wallet (paginated)
2152
 *
2153
 * @param identifier    string      wallet identifier
2154
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2155
 * @param [cb]          function    callback function to call when request is complete
2156
 * @return q.Promise
2157
 */
2158
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2159
    var self = this;
2160
2161
    if (typeof params === "function") {
2162
        cb = params;
2163
        params = null;
2164
    }
2165
2166
    return self.blocktrailClient.get("/wallet/" + identifier + "/utxos", params, true, cb);
2167
};
2168
2169
/**
2170
 * get a paginated list of all wallets associated with the api user
2171
 *
2172
 * @param [params]      object      pagination: {page: 1, limit: 20}
2173
 * @param [cb]          function    callback function to call when request is complete
2174
 * @return q.Promise
2175
 */
2176
APIClient.prototype.allWallets = function(params, cb) {
2177
    var self = this;
2178
2179
    if (typeof params === "function") {
2180
        cb = params;
2181
        params = null;
2182
    }
2183
2184
    return self.blocktrailClient.get("/wallets", params, true, cb);
2185
};
2186
2187
/**
2188
 * verify a message signed bitcoin-core style
2189
 *
2190
 * @param message        string
2191
 * @param address        string
2192
 * @param signature      string
2193
 * @param [cb]          function    callback function to call when request is complete
2194
 * @return q.Promise
2195
 */
2196
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2197
    var self = this;
2198
2199
    var deferred = q.defer();
2200
    deferred.promise.nodeify(cb);
2201
    try {
2202
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2203
        deferred.resolve(result);
2204
    } catch (e) {
2205
        deferred.reject(e);
2206
    }
2207
2208
    return deferred.promise;
2209
};
2210
2211
/**
2212
 * max is 0.001
2213
 * testnet only
2214
 *
2215
 * @param address
2216
 * @param amount
2217
 * @param cb
2218
 */
2219
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2220
    var self = this;
2221
2222
    return self.blocktrailClient.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2223
};
2224
2225
/**
2226
 * send a raw transaction
2227
 *
2228
 * @param rawTransaction    string      raw transaction as HEX
2229
 * @param [cb]              function    callback function to call when request is complete
2230
 * @return q.Promise
2231
 */
2232
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2233
    var self = this;
2234
2235
    return self.blocktrailClient.post("/send-raw-tx", null, rawTransaction, cb);
2236
};
2237
2238
/**
2239
 * get the current price index
2240
 *
2241
 * @param [cb]          function    callback({'USD': 287.30})
2242
 * @return q.Promise
2243
 */
2244
APIClient.prototype.price = function(cb) {
2245
    var self = this;
2246
2247
    return self.blocktrailClient.get("/price", null, false, cb);
2248
};
2249
2250
module.exports = APIClient;
2251